#include "bm_openssl.h"

#include <iostream>
#include <algorithm>

namespace bm {
int make_RSA_key(const std::string& privatePath, const std::string& publicPath, int bits)
{
    RSA* rsa = RSA_new();
    BIGNUM* b1 = BN_new();
    int r = BN_set_word(b1, RSA_F4);
    if (!r)
    {
        RSA_free(rsa);
        BN_free(b1);
        return -1;
    }
    r = RSA_generate_key_ex(rsa, bits, b1, NULL);
    if (!r)
    {
        RSA_free(rsa);
        BN_free(b1);
        return -1;
    }

    if (!privatePath.empty())
    {
        BIO* bp_private = BIO_new_file(privatePath.c_str(), "w+");
        r = PEM_write_bio_RSAPrivateKey(bp_private, rsa, NULL, NULL, 0, NULL, NULL);
        if (!r)
        {
            BIO_free(bp_private);
            RSA_free(rsa);
            BN_free(b1);
            return -1;
        }
        else
            BIO_free(bp_private);
    }

    if (!publicPath.empty())
    {
        BIO* bp_public = BIO_new_file(publicPath.c_str(), "w+");
        r = PEM_write_bio_RSAPublicKey(bp_public, rsa);
        if (!r)
        {
            BIO_free(bp_public);
            RSA_free(rsa);
            BN_free(b1);
            return -1;
        }
        else
            BIO_free(bp_public);
    }

    return 0;
}

int encrypt_RSA(const std::string& from, std::string& to, const std::string& publicKeyPath, bool isPublic)
{
    char* encrypted_data = NULL;
    RSA* rsa_key = NULL;
    FILE* file = NULL;
    int srcLength = 0;
#ifdef _WIN32
    if (fopen_s(&file, publicKeyPath.c_str(), "rb") != 0)
        return -1;
#else
    file = fopen(publicKeyPath.c_str(), "rb");
#endif

    if (!file)
        return -1;

    BIO* bp = BIO_new_file(publicKeyPath.c_str(), "rb");

    rsa_key = PEM_read_bio_RSAPublicKey(bp, NULL, NULL, NULL);
    if (rsa_key == NULL)
    {
        BIO_free(bp);
        return -1;
    }

    srcLength = (int)from.length();
    int rsaLen = RSA_size(rsa_key);
    encrypted_data = new char[rsaLen];
    if (!encrypted_data)
    {
        fclose(file);
        BIO_free(bp);
        return -1;
    }

    memset(encrypted_data, 0, rsaLen);
    int offset = 0;
    int dataLen = rsaLen;
    if (srcLength > rsaLen)
        dataLen = rsaLen - RSA_PKCS1_PADDING_SIZE;

    std::string tmp;
    do
    {
        if (srcLength > rsaLen)
        {
            std::string t = from.substr(offset, dataLen);
            int r = (isPublic ? RSA_public_encrypt : RSA_private_encrypt)(dataLen, (const unsigned char*)t.c_str(), (unsigned char*)encrypted_data, rsa_key, RSA_PKCS1_PADDING);
            if (r < 0)
            {
                delete[] encrypted_data;
                RSA_free(rsa_key);
                fclose(file);
                BIO_free(bp);
                return -1;
            }
            else
            {
                tmp += std::string(encrypted_data, r);

                memset(encrypted_data, 0, rsaLen);
                offset += dataLen;
            }

        }
        else
        {
            int r = (isPublic ? RSA_public_encrypt : RSA_private_encrypt)(dataLen, (const unsigned char*)from.substr(offset, dataLen).c_str(), (unsigned char*)encrypted_data, rsa_key, RSA_PKCS1_PADDING);
            if (r < 0)
            {
                delete[] encrypted_data;
                RSA_free(rsa_key);
                fclose(file);
                BIO_free(bp);
                return -1;
            }
            else
                tmp += std::string(encrypted_data, r);
        }
    } while (offset < srcLength);

    to = tmp;

    delete[] encrypted_data;
    RSA_free(rsa_key);
    fclose(file);
    BIO_free(bp);

    return 0;
}

int decrypt_RSA(const std::string& from, std::string& to, const std::string& privateKeyPath, bool isPublic)
{
    char* decrypted_data = NULL;
    RSA* rsa_key = NULL;
    FILE* file = NULL;
#ifdef _WIN32
    if (fopen_s(&file, privateKeyPath.c_str(), "rb") != 0)
        return -1;
#else
    file = fopen(privateKeyPath.c_str(), "rb");
#endif
    if (!file)
        return -1;

    BIO* bp = BIO_new_file(privateKeyPath.c_str(), "rb");
    if (!bp)
    {
        fclose(file);
        return -1;
    }

    rsa_key = PEM_read_bio_RSAPrivateKey(bp, NULL, NULL, NULL);
    if (rsa_key == NULL)
        return -1;

    int rsaLen = RSA_size(rsa_key);
    decrypted_data = new char[rsaLen];
    if (!decrypted_data)
        return -1;
    memset(decrypted_data, 0, rsaLen);
    int srcLength = (int)from.size();
    int offset = 0;
    std::string tmp;
    do
    {
        if (srcLength > rsaLen)
        {
            int r = (isPublic ? RSA_public_decrypt : RSA_private_decrypt)(rsaLen, (unsigned char*)from.substr(offset, rsaLen).c_str(), (unsigned char*)decrypted_data, rsa_key, RSA_PKCS1_PADDING);
            if (r <= 0)
                return -1;

            tmp += std::string(decrypted_data, r);
            offset += rsaLen;

            memset(decrypted_data, 0, rsaLen);
        }
        else
        {
            int r = (isPublic ? RSA_public_decrypt : RSA_private_decrypt)(rsaLen, (unsigned char*)from.c_str(), (unsigned char*)decrypted_data, rsa_key, RSA_NO_PADDING);
            if (!r)
                return -1;

            tmp += std::string(decrypted_data, r);
            offset += rsaLen;
        }
    } while (offset < srcLength);

    to = tmp;
    delete[] decrypted_data;
    RSA_free(rsa_key);
    fclose(file);
    BIO_free(bp);

    return 0;
}
int encrypt_base64(const std::string& from, std::string& to, bool newLine)
{
    BIO* bmem = NULL;
    BIO* b64 = NULL;
    BUF_MEM* bptr;

    b64 = BIO_new(BIO_f_base64());
    if (!newLine) {
        BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
    }
    bmem = BIO_new(BIO_s_mem());
    b64 = BIO_push(b64, bmem);
    BIO_write(b64, from.c_str(), (int)from.length());
    int r = BIO_flush(b64);
    std::cout << "BIO_flush return:" << r << std::endl;
    BIO_get_mem_ptr(b64, &bptr);
    r = BIO_set_close(b64, BIO_NOCLOSE);

    char* buff = (char*)malloc(bptr->length + 1);
    if (!buff)
        return -1;

    memcpy(buff, bptr->data, bptr->length);
    buff[bptr->length] = 0;
    BIO_free_all(b64);

    to = std::string(buff, bptr->length);
    free(buff);

    return 0;
}
int decrypt_base64(const std::string& from, std::string& to, bool newLine)
{
    BIO* b64 = NULL;
    BIO* bmem = NULL;
    char* buff = (char*)malloc(from.length());
    if (!buff)
        return -1;

    memset(buff, 0, from.length());
    b64 = BIO_new(BIO_f_base64());
    if (!newLine) {
        BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
    }
    bmem = BIO_new_mem_buf(from.c_str(), (int)from.length());
    bmem = BIO_push(b64, bmem);
    int r = BIO_read(bmem, buff, (int)from.length());
    BIO_free_all(bmem);

    to = std::string(buff, r);
    free(buff);

    return 0;
}
#define MD5_LENGTH 16
int md5(const std::string& from, std::string& to)
{
    unsigned char* result = new unsigned char[MD5_LENGTH];
    MD5((const unsigned char*)from.c_str(), from.length(), result);
    if(!result)
    {
        delete[] result;
        return -1;
    }

    for(int i = 0; i < MD5_LENGTH; i++)
    {
        char tmp[3];
        sprintf(tmp, "%02X", result[i]);
        to += tmp;
    }
    std::transform(to.begin(), to.end(), to.begin(), ::tolower);

    delete[] result;

    return 0;
}

int encrypt_aes(const std::string& from, std::string& to, const std::string& key)
{
    if (from.empty() || key.empty()) return 1;

    // //加密的初始化向量
    // unsigned char iv[AES_BLOCK_SIZE] = { 0 };

    // //16位密码
    // char tmpIV[] = "0123456789abcdef";
    // for (int i = 0; i < 16; ++i)
    //     iv[i] = tmpIV[i];

    AES_KEY aes;
    if (AES_set_encrypt_key((const unsigned char*)key.c_str(), 128, &aes) < 0)
    {
        return 1;
    }

    int len = from.length();
    int len_aes;
    if(len % 16 > 0)
        len_aes = len + 16 - (len % 16);
    else len_aes = len;

    // const unsigned char* in = (const unsigned char*)from.c_str();
    unsigned char* in = new unsigned char[len_aes];
    memset(in, 0, len_aes);
    memcpy(in, from.c_str(), len);

    unsigned char* out      = new unsigned char[len_aes];
    memset(out, 0, len_aes);

    // AES_cbc_encrypt((const unsigned char*)from.c_str(), (unsigned char*)out, len, &aes, iv, AES_ENCRYPT);
    for(int i = 0; i < len_aes / 16; i++)
    {
        AES_ecb_encrypt(in + i * 16, out + i * 16, &aes, AES_ENCRYPT);
    }

    to = std::string((char*)out, len_aes);
    delete[] out;
    delete[] in;

    return 0;
}

int decrypt_aes(const std::string& from, std::string& to, const std::string& key)
{
    if (from.empty() || key.empty()) return 1;
    // unsigned char iv[AES_BLOCK_SIZE];//加密的初始化向量
    // char tmpIV[] = "0123456789abcdef";
    // for (int i = 0; i < 16; ++i)
    //     iv[i] = tmpIV[i];
    std::cout << "decrypt_aes set key" << std::endl;
    AES_KEY aes;
    if (AES_set_decrypt_key((unsigned char*)key.c_str(), 128, &aes) < 0)
    {
        return 1;
    }

    int len = from.length();
    int len_aes;
    if(len % 16 > 0)
        len_aes = len + 16 - (len % 16);
    else len_aes = len + 1;

    unsigned char* in = new unsigned char[len_aes];
    memset(in, 0, len_aes);
    memcpy(in, from.c_str(), len);

    unsigned char* out = new unsigned char[len_aes];
    memset(out, 0, len_aes);

    std::cout << "decrypt_aes start decrypt" << std::endl;
    // AES_cbc_encrypt((unsigned char*)str_in, (unsigned char*)out, len, &aes, iv, AES_DECRYPT);
    for(int i = 0; i < len_aes / 16; i++)
    {
        AES_ecb_encrypt(in + i * 16, out + i * 16, &aes, AES_DECRYPT);
    }

    std::cout << "decrypt_aes end decrypt" << std::endl;
    to = std::string((char*)out);
    delete[] in;
    delete[] out;

    return 0;
}

int encrypt_aes_cbc(const std::string& from, std::string& to, const std::string& key)
{
    if(key.length() < AES_BLOCK_SIZE)
        return -1;

    uint8_t iv[AES_BLOCK_SIZE] = {0};
    for(int i = 0; i < AES_BLOCK_SIZE; i++)
        iv[i] = key[i];

    try{

        EVP_CIPHER_CTX* ctx = NULL;
        // uint8_t const key[] = "key";
        int written = 0;
        uint32_t bytes = 0;
        int outLen = from.length() + 1;
        unsigned char *out = new unsigned char[outLen + 16];
        memset(out, 0, outLen  + 16);

        // 1.create the cipher context
        if ((ctx = EVP_CIPHER_CTX_new()) == NULL)
        {
            std::cout << "EVP_CIPHER_CTX_new error" << std::endl;
            return -1;
        }

        // 2.sets up cipher context ctx for encryption
        if (EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, (unsigned char*)key.c_str(), iv) != 1)
        {
            std::cout << "EVP_EncryptInit_ex error" << std::endl;
            return -1;
        }

        written = 0;
        // 3.encrypts inl bytes from the buffer in and writes the encrypted version to out.
        if (EVP_EncryptUpdate(ctx, out, &written, (unsigned char*)from.c_str(), from.length()) != 1)
        {
            std::cout << "EVP_EncryptUpdate error" << std::endl;
            return -1;
        }

        bytes = written;
        written = 0;
        // 4.If padding is enabled (the default) then EVP_EncryptFinal_ex() encrypts the "final" data, that is any data that remains in a partial block. It uses standard block padding (aka PKCS padding) as described in the NOTES section, below. The encrypted final data is written to out which should have sufficient space for one cipher block. The number of bytes written is placed in outl. After this function is called the encryption operation is finished and no further calls to EVP_EncryptUpdate() should be made.
        if (EVP_EncryptFinal_ex(ctx, out + bytes, &written) != 1)
        {
            std::cout << "EVP_EncryptFinal_ex error" << std::endl;
            return -1;
        }
        bytes += written;

        to = std::string((char*)out, bytes);

        delete[] out;

    } catch (...) {
        std::cout << "encrypt_aes_cbc exception" << std::endl;
    }

    return 0;
}

// /**
//  * @brief AES::cbc_encrypt
//  * CBC模式加解密，填充模式采用PKCS7Padding，
//  * 支持对任意长度明文进行加解密。
//  * @param in 输入数据
//  * @param out 输出结果
//  * @param key 密钥，长度必须是16/24/32字节，否则加密失败
//  * @param ivec 初始向量，长度必须是16字节
//  * @param enc true-加密，false-解密
//  * @return 执行结果
//  */
// int cbc_encrypt(const std::string& from, std::string& to, const std::string& key, bool enc)
// {
//     // 检查密钥合法性(只能是16、24、32字节)
//     if(key.size() != 16 && key.size() != 24 && key.size() != 32)
//         return -1;
//     // assert(ivec.size() == 16); // 初始向量为16字节

//     if (enc)
//     {
//         // 生成加密key
//         AES_KEY aes_key;
//         if (AES_set_encrypt_key((const unsigned char*)key.data(), key.size() * 8, &aes_key) != 0)
//         {
//             return false;
//         }

//         // 进行PKCS7Padding填充
//         QByteArray inTemp = Padding::PKCS7Padding(in, AES_BLOCK_SIZE);

//         // 执行CBC模式加密
//         QByteArray ivecTemp = ivec; // ivec会被修改，故需要临时变量来暂存
//         out.resize(inTemp.size()); // 调整输出buf大小
//         AES_cbc_encrypt((const unsigned char*)inTemp.data(),
//                         (unsigned char*)out.data(),
//                         inTemp.size(),
//                         &aes_key,
//                         (unsigned char*)ivecTemp.data(),
//                         AES_ENCRYPT);
//         return true;
//     }
//     else
//     {
//         // 生成解密key
//         AES_KEY aes_key;
//         if (AES_set_decrypt_key((const unsigned char*)key.data(), key.size() * 8, &aes_key) != 0)
//         {
//             return false;
//         }

//         // 执行CBC模式解密
//         QByteArray ivecTemp = ivec; // ivec会被修改，故需要临时变量来暂存
//         out.resize(in.size()); // 调整输出buf大小
//         AES_cbc_encrypt((const unsigned char*)in.data(),
//                         (unsigned char*)out.data(),
//                         in.size(),
//                         &aes_key,
//                         (unsigned char*)ivecTemp.data(),
//                         AES_DECRYPT);

//         // 解除PKCS7Padding填充
//         out = Padding::PKCS7UnPadding(out);
//         return true;
//     }
// }

int decrypt_aes_cbc(const std::string& from, std::string& to, const std::string& key)
{
    unsigned char iv[AES_BLOCK_SIZE];//加密的初始化向量
    // char tmpIV[] = key;
    for (int i = 0; i < 16; ++i)
        iv[i] = key[i];

    EVP_CIPHER_CTX *ctx = NULL;
    // char *in = base64Decode(src, srcLen, 0);
    // unsigned char iv[AES_BLOCK_SIZE] = {0};
    int outLen = 0;
    unsigned char *out = new unsigned char[from.length() + 1];
    memset(out, 0, from.length() + 1);
    do {
        /* Create and initialise the context */
        if(!(ctx = EVP_CIPHER_CTX_new())) {
            // handleOpenSSLErrors();
            break;
        }

        /* Initialise the encryption operation. IMPORTANT - ensure you use a key
        * and IV size appropriate for your cipher
        * In this example we are using 256 bit AES (i.e. a 256 bit key). The
        * IV size for *most* modes is the same as the block size. For AES this
        * is 128 bits */
        if(1 != EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, (unsigned char*)key.c_str(), iv)) {
            // handleOpenSSLErrors();
            break;
        }

        if(1 != EVP_DecryptUpdate(ctx, out, &outLen, (const unsigned char*)from.c_str(), from.length())) {
            // handleOpenSSLErrors();
            break;
        }
    }while(0);

    // free(src);
    if (ctx != NULL)
        EVP_CIPHER_CTX_free(ctx);

    //PKCS5 UNPADDING
    if(outLen < int(from.length()))
        outLen = from.length();

    int unpadding = out[outLen - 1];
    if(outLen < unpadding || outLen < 0 || unpadding < 0)
    {
        std::cout << __FUNCTION__ << "::" << __LINE__ << " data error src data len=" << from.length() << ",outLen:" << outLen << ",unpadding:" << unpadding << std::endl;
        return -1;
    }

    outLen = outLen - unpadding;
    out[outLen] = '\0';
    to = std::string((char*)out, outLen);
    delete[] out;

    return 0;
}

}